<?php
declare(strict_types=1);

namespace App\Utils\Other\Office\Excel;

use Hyperf\Context\Context;
use Hyperf\Coroutine\Coroutine;
use Hyperf\HttpMessage\Stream\SwooleStream;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Exception;
use App\Exception\InvalidRequestException;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Color;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\Csv;
use PhpOffice\PhpSpreadsheet\Writer\Xls;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
use function Hyperf\Coroutine\defer;

Class Export {

    /**
     * @var Worksheet[]
     */
    protected array $sheet;

    /**
     * @var Spreadsheet[]
     */
    protected array $spreadsheet;

    /**
     * 行指针
     * @var int[]
     */
    protected array $row;

    /**
     * 设置响应头
     * @var array|string[]
     */
    protected array $headers = [
        'xls'=>'application/vnd.ms-excel',
        'xlsx'=>'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        'csv'=>'application/vnd.ms-excel; charset=gb18030'
    ];

    /**
     * 初始化
     * @param string $title
     * @return $this
     * @throws Exception
     */
    public function init(string $title=''):static
    {
        $this->setSpreadsheet(new Spreadsheet());
        $this->setActiveSheetIndex(0, false, $title); // 设置第一页
        return $this;
    }

    /**
     * 获取协程ID
     * @return int
     */
    public function getCoroutineId():int
    {
        return Coroutine::id();
    }

    /**
     * 设置Spreadsheet
     * @param $spreadsheet
     * @return $this
     */
    public function setSpreadsheet($spreadsheet):static
    {
        $this->spreadsheet[$this->getCoroutineId()] = $spreadsheet;
        return $this;
    }

    /**
     * 获取Spreadsheet
     * @return Spreadsheet
     */
    public function getSpreadsheet():Spreadsheet
    {
        return $this->spreadsheet[$this->getCoroutineId()];
    }

    /**
     * 切换工作表
     * @param int $index
     * @param bool $create
     * @param string $title
     * @return $this
     * @throws Exception
     */
    public function setActiveSheetIndex(int $index=0, bool $create=true, string $title=''):static
    {
        $this->setRow(1); //切换工作表重置指针
        if ($create) {
            $this->spreadsheet[$this->getCoroutineId()]->createSheet(); // 新增工作表
        }
        $this->spreadsheet[$this->getCoroutineId()]->setActiveSheetIndex($index);
        $this->setSheet($this->getSpreadsheet()->getActiveSheet());
        if ($title) {
            $this->sheet[$this->getCoroutineId()]->setTitle($title); // 设置工作表标签
        }

        return $this;
    }

    /**
     * 设置行指针
     * @param int|null $row
     */
    public function setRow(int $row = null):void
    {
        if (null === $row) {
            $this->row[$this->getCoroutineId()] += 1;
        } else {
            $this->row[$this->getCoroutineId()] = $row;
        }
    }

    /**
     * 获取当前行指针
     * @return int
     */
    public function getRow():int
    {
        return $this->row[$this->getCoroutineId()];
    }

    /**
     * 重新设置当前sheet
     * @param Worksheet $sheet
     */
    private function setSheet(Worksheet $sheet):void
    {
        $this->sheet[$this->getCoroutineId()] = $sheet;
    }

    /**
     * 获取Sheet
     * @return Worksheet
     */
    public function getSheet():Worksheet
    {
        return $this->sheet[$this->getCoroutineId()];
    }

    /**
     * 获取当前行数
     * @return int
     */
    public function getCurrentRow():int
    {
        return $this->getRow() - 1;
    }

    /**
     * 设置列宽
     * @param $width 30|[60, 30, 40]
     * @param null $column 30|null|['A', null,['B','C','D']]
     * @return $this
     */
    public function setWidth($width, $column=null):static
    {
        if ($column === null) {
            $this->sheet[$this->getCoroutineId()]->getDefaultColumnDimension()->setWidth($width);
            return $this;
        }
        $column = (array)$column;

        if (!is_array($width)) {
            foreach ($column as $_column) {
                $this->sheet[$this->getCoroutineId()]->getColumnDimension($_column)->setWidth($width);
            }
            return $this;
        }

        $count = min(count($width), count($column));
        for ($i = 0; $i < $count; $i++) {
            if (!array_key_exists($i, $column)) {
                throw new InvalidRequestException('Column Not Found');
            }
            if ($column[$i] === null) {
                $this->sheet[$this->getCoroutineId()]->getDefaultColumnDimension()->setWidth($width[$i]);
            } else {
                if (!is_array($column[$i])) {
                    $column[$i] = (array)$column[$i];
                }
                foreach ($column[$i] as $_column) {
                    $this->sheet[$this->getCoroutineId()]->getColumnDimension($_column)->setWidth($width[$i]);
                }
            }
        }

        return $this;
    }

    /**
     * 设置行高
     * @param $height 30|[30,60]
     * @param null $row 30|null|[1,null,[2,3]]
     * @return $this
     */
    public function setHeight($height, $row=null):static
    {
        if (null === $row) {
            $this->sheet[$this->getCoroutineId()]->getDefaultRowDimension()->setRowHeight($height);
            return $this;
        }
        $row = (array)$row;
        if (!is_array($height)) {
            foreach ($row as $_row) {
                $this->sheet[$this->getCoroutineId()]->getRowDimension($_row)->setRowHeight($height);
            }
            return $this;
        }

        $count = min(count($height), count($row));
        for ($i = 0; $i < $count; $i++) {
            if (!array_key_exists($i, $row)) {
                throw new InvalidRequestException('Row Not Found');
            }
            if (null === $row[$i]) {
                $this->sheet[$this->getCoroutineId()]->getDefaultRowDimension()->setRowHeight($height[$i]);
            } else {
                if (!is_array($row[$i])) {
                    $row[$i] = (array)$row[$i];
                }
                foreach ($row[$i] as $_row) {
                    $this->sheet[$this->getCoroutineId()]->getRowDimension($_row)->setRowHeight($height[$i]);
                }
            }
        }

        return $this;
    }

    /**
     * 设置类型
     * @param $cells
     * @param array $styles
     * @return $this
     */
    public function setType($cells, array $styles):static
    {
        if (!is_array($cells)) {
            $cells = (array)$cells;
        }
        foreach ($cells as $cell) {
            $this->sheet[$this->getCoroutineId()]->getStyle($cell)->applyFromArray($styles);
        }

        return $this;
    }

    /**
     * 设置文本自动换行
     * @param $cells
     * @return $this
     */
    public function setWrap($cells):static
    {
        if (!is_array($cells)) {
            $cells = (array)$cells;
        }
        foreach ($cells as $cell) {
            $this->sheet[$this->getCoroutineId()]->getStyle($cell)->getAlignment()->setWrapText(true);
        }
        return $this;
    }

    /**
     * 设置单元格颜色
     * @param $cells
     * @param string $color
     * @param string $fill
     * @return $this
     */
    public function setFill($cells, string $color='FFFFFF', string $fill=Fill::FILL_SOLID):static
    {
        $cells = (array)$cells;
        foreach ($cells as $cell) {
            $this->sheet[$this->getCoroutineId()]->getStyle($cell)->getFill()->setFillType($fill)->getStartColor()->setRGB($color);
        }

        return $this;
    }

    /**
     * 设置字体对齐方式
     * @param $cells
     * @param string $alignment
     * @return $this
     */
    public function setAlignment($cells, string $alignment=Alignment::HORIZONTAL_LEFT):static
    {
        $cells = (array)$cells;
        $styleArray = [
            'alignment' => [
                'horizontal' => $alignment,
                'vertical' => Alignment::VERTICAL_CENTER
            ],
        ];
        foreach ($cells as $cell) {
            $this->sheet[$this->getCoroutineId()]->getStyle($cell)->applyFromArray($styleArray);
        }

        return $this;
    }

    /**
     * 设置字体
     * @param $cells
     * @param int $size
     * @param bool $bold
     * @param string $color
     * @param string $name
     * @return $this
     */
    public function setFont($cells, int $size=11, bool $bold=false, string $color=Color::COLOR_BLACK, string $name='微软雅黑'):static
    {
        $cells = (array)$cells;
        foreach ($cells as $cell) {
            $this->sheet[$this->getCoroutineId()]->getStyle($cell)->getFont()->setBold($bold)->setName($name)->setSize($size)->getColor()->setRGB($color);
        }

        return $this;
    }

    /**
     * 设置表格边框样式
     * @param $cells
     * @param string $borders
     * @param string $color
     * @return $this
     * @throws Exception
     */
    public function setBorder($cells, string $borders=Border::BORDER_THIN, string $color=Color::COLOR_BLACK):static
    {
        if (!is_array($cells)) {
            $cells = (array)$cells;
        }
        foreach ($cells as $cell) {
            $this->sheet[$this->getCoroutineId()]->getStyle($cell)->getBorders()->getAllBorders()->setBorderStyle($borders)->setColor(new Color($color));
        }

        return $this;
    }

    /**
     * 设置单元格格式
     * @param $cells
     * @return $this
     */
    public function setCellType($cells, string $formatCode=NumberFormat::FORMAT_TEXT):static
    {
        if (!is_array($cells)) {
            $cells = (array)$cells;
        }
        foreach ($cells as $cell) {
            $this->sheet[$this->getCoroutineId()]
                ->getStyle($cell)
                ->getNumberFormat()
                ->setFormatCode($formatCode);
        }

        return $this;
    }

    /**
     * 设置标题
     * @param array $titles
     * @param int $row
     * @return $this
     */
    public function setTitle(array $titles, int $row=0):static
    {
        if ($row > 0) $this->setRow($row);
        foreach ($titles as $column => $title) {
            $column++;
            $this->sheet[$this->getCoroutineId()]->setCellValue([$column, $this->getRow()], $title);
        }
        $this->setRow();

        return $this;
    }

    /**
     * 设置内容
     * @param array $bodys
     * @param array $type
     * @param int $row
     * @return $this
     */
    public function setBody(array $bodys, array $type=[], int $row=0):static
    {
        if ($row > 0) $this->setRow($row);

        $column = count($bodys);
        for ($i = 0; $i < $column; $i++) {
            foreach ($bodys[$i] as $columnIndex => $body) {
                $dataType = $type[$columnIndex]??DataType::TYPE_STRING;
                $columnIndex++;
                $this->sheet[$this->getCoroutineId()]->setCellValueExplicit([$columnIndex, $this->getRow()], $body, $dataType);
            }
            $this->setRow();
        }

        return $this;
    }

    /**
     * 合并单元格
     * @param $column1
     * @param $row1
     * @param $column2
     * @param $row2
     * @return $this
     * @throws \Exception
     */
    public function mergeCells($column1, $row1, $column2, $row2):static
    {
        $this->sheet[$this->getCoroutineId()]->mergeCells([$column1, $row1, $column2, $row2]);
        return $this;
    }

    /**
     * 下载表格
     * @param string $filename
     * @param string $suffix
     * @param string $pFilename
     * @return Psr7ResponseInterface|void
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
     */
    public function download(string $filename='表格', string $suffix='xlsx', string $pFilename='php://output')
    {
        $suffix = strtolower($suffix);

        if ($suffix === 'xlsx') {
            $writer = new Xlsx($this->getSpreadsheet());
        } elseif ($suffix === 'xls') {
            $writer = new Xls($this->getSpreadsheet());
        } else {
            $writer = new Csv($this->getSpreadsheet());
        }

        defer(function () {
            // 销毁实例
            $this->spreadsheet[$this->getCoroutineId()]->disconnectWorksheets();
            $this->sheet[$this->getCoroutineId()]->disconnectCells();
            unset(
                $this->spreadsheet[$this->getCoroutineId()],
                $this->sheet[$this->getCoroutineId()],
                $this->row[$this->getCoroutineId()]
            );
        });

        if ($pFilename !== 'php://output') {
            $writer->save($pFilename); // 保存到磁盘
            return ;
        }

        // 输出到浏览器
        $response = Context::get(Psr7ResponseInterface::class);
        $headers['Content-Type'] = $this->headers[$suffix];
        $headers['Content-Disposition'] = 'attachment;filename="'.$filename.'.'.$suffix.'"';
        $headers['Cache-Control'] = 'max-age=0';
        $headers['Pragma'] = 'public';
        $headers['Content-transfer-encoding'] = 'binary';
        foreach ($headers as $name => $header) {
            $response = $response->withHeader($name, $header);
            Context::set(Psr7ResponseInterface::class, $response);
        }

        ob_start();
        $writer->save($pFilename);
        $content = ob_get_clean();

        return $response->withBody(new SwooleStream($content));
    }
}